"""
low memory dictionary.
save data in swap files, in order to save memory
"""

import pprint
import pickle
import os,sys,random

def tmpfile():
    file = '/tmp/lowMemDict_'+str(random.random() * 1000000000000 )
    while( os.path.exists( file ) ):
        file = '/tmp/lowMemDict_'+str(random.random() * 1000000000000 )
    
    return file


class LowMemDict(dict):
    def __init__(self,maxin=500000):
        """
        low memory usage dictionary.
        maxin : max insert for each swap file
        with default parameter, the maximum memory occupied is ~ 12 MB
        """
        
        dict.__init__(self)
        self.__maxin = maxin 
        self.__nin = 0 #n of inserted elements
        #generate the first swap
        self.__swaps = [tmpfile()] #list of swaps file
        
    def keys(self):
        sys.stderr.write( "warning! method very low, deprecated! use iterkeys\n" )
        ls = []
        
        for f in self.__swaps:
            try:
                tmpdic = pickle.load(file(f))
            except IOError:
                raise IOError("Warning!!! the swap files may be corrupted! some of them were deleted! you have loss some of your data :(")
            ls += dict.keys(tmpdic)
    
        return ls+dict.keys(self)

    def values(self):
        sys.stderr.write( "warning! method very low, deprecated! use itervalues\n" )
        ls = []
        
        for f in self.__swaps:
            try:
                tmpdic = pickle.load(file(f))
            except IOError:
                raise IOError("Warning!!! the swap files may be corrupted! some of them were deleted! you have loss some of your data :(")
            ls += dict.values(tmpdic)
    
        return ls+dict.values(self)


    def iterkeys(self):
        """
        we give you the keys from the newest to the oldest
        """
        for k in dict.iterkeys(self):
            yield k
            
        for i in xrange(len(self.__swaps)-2,-1,-1):
            try:
                tmpdic = pickle.load(file(self.__swaps[i]))
            except IOError:
                raise IOError("Warning!!! the swap files may be corrupted! some of them were deleted! you have loss some of your data :(")
            
            for k in dict.iterkeys(tmpdic):
                yield k

    
    def itervalues(self):
        """
        we give you the values from the newest to the oldest
        """
        for k in dict.itervalues(self):
            yield k
            
        for i in xrange(len(self.__swaps)-2,-1,-1):
            try:
                tmpdic = pickle.load(file(self.__swaps[i]))
            except IOError:
                raise IOError("Warning!!! the swap files may be corrupted! some of them were deleted! you have loss some of your data :(")
            
            for k in dict.itervalues(tmpdic):
                yield k


    def items(self,all=False):
        sys.stderr.write("Warning! if I return the whole data probably you obtain a MemoryError..\n")
        ls = []

        for f in self.__swaps:
            try:
                tmpdic = pickle.load(file(f))
            except IOError:
                raise IOError("Warning!!! the swap files may be corrupted! some of them were deleted! you have loss some of your data :(")
            ls += dict.items(tmpdic)
        
        return ls + dict.items(self)

    def size(self):
        """
        show the size in memory (in byte) of dictionary
        """
        return self.__sizeof__() + self.__maxin.__sizeof__() + self.__nin.__sizeof__() + self.__swaps.__sizeof__()
   
    def __delitem__(self, k):
        """
        x.__delitem__(k) <==> del x[k]
        """
        if self.has_key(k):
            self.__nin -=1
            dict.__delitem__(self,k)
        else:
            raise IOError("you cannot remove from old swapped data" )
        
    def __getitem__(self, k):
        """
        x.__getitem__(k) <==> x[k]
        """
        
        if self.has_key(k): 
            return self.get(k,None)
            
        #iter each file :'(
                         # -2 because I have to skip the last that it isn't written yet
        for i in xrange(len(self.__swaps)-2,-1,-1):
            try:
                tmpdic = pickle.load(file(self.__swaps[i]))
            except IOError:
                raise IOError("Warning!!! the swap files may be corrupted! some of them were deleted! you have loss some of your data :(")
            if tmpdic.has_key(k):
                return tmpdic[k]

        raise KeyError( str(k)+' not present in dictionary' )


    def __setitem__(self,k,v):
        """
        x.__setitem__(i, y) <==> x[i]=y
        """
        
        if self.has_key(k): #replace it and return
            dict.__setitem__(self,k,v)
            return
        
        if self.__nin >= self.__maxin:
            #too may memory occupied! flush on swap
            f = file( self.__swaps[-1] , 'w' )
            pickle.dump(self,f)
            f.close()
            #use another tmp file
            self.__swaps.append( tmpfile() )
            self.clear() #reintialize (clear data)
            self.__nin = 0
            
        dict.__setitem__(self,k,v)
        self.__nin +=1


    def __str__(self):
        """
        return only some of the data
        """
        return pprint.pformat(self)

    def __del__(self):
        for f in self.__swaps:
            os.remove( f )


